package com.sanji.audio_test

import android.content.Context
import android.media.*
import android.net.Uri
import android.telecom.Call
import android.util.Log

/**
 * 用于在通话过程中播放音频文件的线程类。
 * 
 * 该线程负责解码音频文件并通过电话输出设备播放音频，
 * 支持在播放过程中取消操作。
 * 
 * @param context 应用上下文，用于访问系统服务和资源
 * @param listener 播放完成或失败的回调监听器
 * @param call 相关联的通话对象
 */
class PlayerThread(
    private val context: Context,
    private val listener: OnPlaybackCompletedListener,
    call: Call,
) : Thread(PlayerThread::class.java.simpleName) {

    /** 用于日志记录的标签，包含线程ID以区分不同实例 */
    private val tag = "${PlayerThread::class.java.simpleName}/${id}"

    /** 应用偏好设置管理器，用于获取音频文件路径等配置 */
    private val prefs = Preferences(context)
    
    /** 线程状态标志，标记是否已取消播放 */
    @Volatile private var isCancelled = false
    
    /** 捕获失败标志，标记播放过程中是否发生错误 */
    private var captureFailed = false

    init {
        Log.i(tag, "为通话创建线程: $call")
    }

    /**
     * 线程主执行方法。
     * 
     * 负责控制播放流程，处理异常情况，并在播放完成后回调相应监听器方法。
     * 播放成功时调用 [OnPlaybackCompletedListener.onPlaybackCompleted]，
     * 失败时调用 [OnPlaybackCompletedListener.onPlaybackFailed]。
     */    
    override fun run() {
        var success = false
        var errorMsg: String? = null

        try {
            Log.i(tag, "播放线程已启动")

            if (isCancelled) {
                Log.i(tag, "播放在线程开始前已被取消")
            } else {
                playUntilCancelled()

                success = !captureFailed
            }
        } catch (e: Exception) {
            Log.e(tag, "播放过程中出错", e)
            errorMsg = e.localizedMessage
        } finally {
            Log.i(tag, "播放线程已完成")
            if (success) {
                listener.onPlaybackCompleted(this)
            } else {
                listener.onPlaybackFailed(this, errorMsg)
            }
        }
    }    

    /**
     * 取消播放。这将在处理下一个最小缓冲区大小后停止播放音频。
     *
     * 如果在 [start] 之前调用，则不会初始化音频输出设备。
     */
    fun cancel() {
        isCancelled = true
    }  


    /**
     * 持续播放音频直到调用 [cancel] 或发生音频播放错误。
     * 
     * 该方法负责完整的播放流程：
     * 1. 查找电话输出设备
     * 2. 打开并解码音频文件
     * 3. 配置 AudioTrack
     * 4. 执行播放循环
     * 5. 释放资源
     * 
     * @throws Exception 如果在初始化或播放过程中发生错误
     */
    private fun playUntilCancelled() {
        // 查找电话输出设备
        val audioManager = context.getSystemService(AudioManager::class.java)
        val telephonyOutput = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
            .find { it.type == AudioDeviceInfo.TYPE_TELEPHONY }
            ?: throw Exception("未找到电话输出音频设备")

        val openingStatement = prefs.openingStatement ?: throw Exception("未选择开场白文件")
        val extractor = openAudioFile(openingStatement)

        // 我的RAII在哪里？ :(
        try {
            val codec = createDecoder(extractor)

            try {
                codec.start()

                val channelCount = codec.inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
                val channelMask = when (channelCount) {
                    1 -> AudioFormat.CHANNEL_OUT_FRONT_LEFT
                    2 -> AudioFormat.CHANNEL_OUT_FRONT_LEFT or AudioFormat.CHANNEL_OUT_FRONT_RIGHT
                    else -> throw Exception("不支持的声道数: $channelCount")
                }

                val audioFormat = AudioFormat.Builder()
                    .setSampleRate(codec.inputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE))
                    .setChannelMask(channelMask)
                    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                    .build()
                val minBufSize = AudioTrack.getMinBufferSize(
                    audioFormat.sampleRate,
                    audioFormat.channelMask,
                    audioFormat.encoding,
                )
                if (minBufSize < 0) {
                    throw Exception("查询最小缓冲区大小时失败: $minBufSize")
                }

                Log.d(tag, "AudioTrack 最小缓冲区大小: $minBufSize")

                // 创建音频输出轨道
                val attributes = AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
                    .build()
                val audioTrack = AudioTrack.Builder()
                    .setBufferSizeInBytes(minBufSize)
                    .setAudioAttributes(attributes)
                    .setAudioFormat(audioFormat)
                    .build()

                Log.d(tag, "AudioTrack 格式: ${audioTrack.format}")

                try {
                    if (!audioTrack.setPreferredDevice(telephonyOutput)) {
                        throw Exception("设置首选输出设备失败")
                    }

                    audioTrack.play()
                    if (audioTrack.playState != AudioTrack.PLAYSTATE_PLAYING) {
                        throw Exception("AudioTrack 处于错误状态: ${audioTrack.playState}")
                    }

                    try {
                        playbackLoop(audioTrack, codec, extractor)
                    } finally {
                        audioTrack.stop()
                    }
                } finally {
                    audioTrack.release()
                }
            } finally {
                codec.release()
            }
        } finally {
            extractor.release()
        }
    }  

    /**
     * 播放通话音频的主循环。
     *
     * 循环会一直运行直到调用 [cancel]。取消的大致时间是处理最小缓冲区大小所需的时间。
     *
     * @param track 必须已调用 [AudioTrack.play]
     * @param codec 必须已调用 [MediaCodec.start]
     * @param extractor 必须已选择轨道
     *
     * @throws Exception 如果在音频播放或解码过程中发生错误
     */
    private fun playbackLoop(track: AudioTrack, codec: MediaCodec, extractor: MediaExtractor) {
        while (!isCancelled) {
            var waitForever = false

            val inputBufferId = codec.dequeueInputBuffer(TIMEOUT)
            if (inputBufferId >= 0) {
                val inputBuffer = codec.getInputBuffer(inputBufferId)!!

                val n = extractor.readSampleData(inputBuffer, 0)

                val flags = if (n < 0) {
                    Log.d(tag, "正在处理最终缓冲区; 提交EOF")
                    waitForever = true
                    isCancelled = true
                    MediaCodec.BUFFER_FLAG_END_OF_STREAM
                } else {
                    0
                }

                codec.queueInputBuffer(
                    inputBufferId,
                    0,
                    inputBuffer.limit(),
                    extractor.sampleTime,
                    flags,
                )

                extractor.advance()
            } else {
                Log.w(tag, "意外的输入缓冲区出队错误: $inputBufferId")
            }

            flushOutput(track, codec, waitForever)
        }
    }


    /**
     * 将编解码器中的排队PCM输出刷新到音频轨道。
     * 
     * 从 MediaCodec 获取解码后的音频数据并写入 AudioTrack 进行播放。
     * 处理各种缓冲区状态和格式变化事件。
     *
     * @param track 用于播放音频的 AudioTrack 实例
     * @param codec 解码音频数据的 MediaCodec 实例
     * @param waitForever 是否无限期等待输出缓冲区
     * @throws Exception 如果在音频播放过程中发生错误
     */
    private fun flushOutput(track: AudioTrack, codec: MediaCodec, waitForever: Boolean) {
        val bufferInfo = MediaCodec.BufferInfo()

        while (true) {
            val timeout = if (waitForever) { 
                -1 
            } else { 
                TIMEOUT 
            }
            val outputBufferId = codec.dequeueOutputBuffer(bufferInfo, timeout)
            if (outputBufferId >= 0) {
                val buffer = codec.getOutputBuffer(outputBufferId)!!

                val n = track.write(buffer, bufferInfo.size, AudioTrack.WRITE_BLOCKING)
                if (n < 0) {
                    throw Exception("写入音频轨道失败: $n")
                }

                codec.releaseOutputBuffer(outputBufferId, false)

                if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
                    Log.d(tag, "收到EOF; 已完全刷新")
                    // 输出已完成写入
                    break
                }
            } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                Log.d(tag, "输出格式更改为: ${codec.outputFormat}")
            } else if (outputBufferId == MediaCodec.INFO_TRY_AGAIN_LATER) {
                break
            } else {
                Log.w(tag, "意外的输出缓冲区出队错误: $outputBufferId")
                break
            }
        }
    }


    /**
     * 打开指定URI的音频文件并创建 MediaExtractor 实例。
     * 
     * @param uri 音频文件的URI路径
     * @return 配置好的 MediaExtractor 实例
     */
    private fun openAudioFile(uri: Uri): MediaExtractor = MediaExtractor().apply {
        setDataSource(context, uri, null)
    }

    /**
     * 为给定的 MediaExtractor 创建并配置合适的音频解码器。
     * 
     * 遍历所有轨道寻找音频轨道，并为第一个找到的音频轨道创建解码器。
     * 
     * @param extractor 包含音频文件数据的 MediaExtractor
     * @return 配置好的 MediaCodec 解码器实例
     * @throws Exception 如果找不到合适的解码器或配置失败
     */
    private fun createDecoder(extractor: MediaExtractor): MediaCodec {
        val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)

        for (i in 0 until extractor.trackCount) {
            val trackFormat = extractor.getTrackFormat(i)

            // 如果有采样率则为音频格式
            if (!trackFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE)) {
                Log.d(tag, "跳过非音频格式: $trackFormat")
                continue
            }

            val decoder = codecList.findDecoderForFormat(trackFormat)
            if (decoder == null) {
                Log.w(tag, "未找到解码器: $trackFormat")
                continue
            }

            Log.d(tag, "音频解码器: $decoder")

            val codec = MediaCodec.createByCodecName(decoder)

            try {
                codec.configure(trackFormat, null, null, 0)
                extractor.selectTrack(i)
                return codec
            } catch (e: Exception) {
                Log.w(tag, "配置编解码器失败: $trackFormat", e)
                codec.release()
            }
        }

        throw Exception("没有解码器能处理输入文件")
    }

    companion object {
        private const val TIMEOUT = 500L
    }

    /**
     * 播放完成监听器接口。
     * 
     * 定义播放成功完成或失败时的回调方法。
     */
    interface OnPlaybackCompletedListener {
        /**
         * 当播放成功完成时调用。
         * 
         * @param thread 完成播放的 PlayerThread 实例
         */
        fun onPlaybackCompleted(thread: PlayerThread)

        /**
         * 当播放过程中发生错误时调用。
         * 
         * @param thread 发生错误的 PlayerThread 实例
         * @param errorMsg 错误信息描述
         */
        fun onPlaybackFailed(thread: PlayerThread, errorMsg: String?)
    }
}